package grouuu.tile;

import grouuu.data.Context;
import grouuu.data.TileData;
import grouuu.tile.Tiles.Node;
import haxe.Json;
import openfl.Assets;
import openfl.display.BitmapData;
import openfl.display.Graphics;
import openfl.display.Tilesheet;
import openfl.geom.Matrix;
import openfl.geom.Point;
import openfl.geom.Rectangle;

/**
 * ...
 * @author Grouuu
 */
class Tiles
{
	private var context:Context;
	
	private var map:BitmapData;
	private var tiles:Tilesheet;
	private var tilesData:Map<Int, TileData>;
	private var bufferTiles:Array<Array<BitmapData>>;

	public function new(context:Context) 
	{
		this.context = context;
		
		bufferTiles = [];
		
		init();
	}
	
	private function init():Void
	{
		map = getMap();
		tiles = getTiles();
		tilesData = getTilesData();
	}
	
	private function getMap():BitmapData
	{
		return Assets.getBitmapData("img/map.png");
	}
	
	private function getTiles():Tilesheet
	{
		return new Tilesheet(Assets.getBitmapData("img/tiles.png"));
	}
	
	private function getTilesData():Map<Int, TileData>
	{
		var listTiles:Array<Dynamic> = Reflect.field(Json.parse(Assets.getText("data/map.txt")), "tiles");
		var mapTiles:Map<Int, TileData> = new Map<Int, TileData>();
		
		var tile:TileData;
		
		for (t in listTiles)
		{
			tile = new TileData();
			
			tile.name = Reflect.field(t, "name");
			tile.col = Reflect.field(t, "col");
			tile.row = Reflect.field(t, "row");
			tile.solid = (Reflect.field(t, "solid") == "1");
			tile.setColor(Reflect.field(t, "color"));
			tile.id = tiles.addTileRect(new Rectangle(tile.col * context.tileW, tile.row * context.tileH, context.tileW, context.tileH));
			
			mapTiles.set(tile.color, tile);
		}
		
		return mapTiles;
	}
	
	public function drawPart(src:Graphics, posX:Float, posY:Float, nbTileCol:Int, nbTileRow:Int):Void
	{
		var listTiles:Array<Float> = [];
		
		var col:Int = Std.int(posX / context.tileW) - 1;
		var row:Int = Std.int(posY / context.tileH) - 1;
		
		posX = Std.int(posX - col * context.tileW);
		posY = Std.int(posY - row * context.tileH);
		
		nbTileCol += 2;
		nbTileRow += 2;
		
		var tileCol:Int;
		var tileRow:Int;
		var color:Int;
		var data:TileData;
		
		for (c in 0...nbTileCol)
		{
			for (r in 0...nbTileRow)
			{
				tileCol = col + c;
				tileRow = row + r;
				
				color = map.getPixel(tileCol, tileRow);
				
				data = tilesData[color];
				
				listTiles = listTiles.concat([c * context.tileW - posX, r * context.tileH - posY, data.id]);
			}
		}
		
		tiles.drawTiles(src, listTiles);
	}
	
	public function getTile(tileX:Int, tileY:Int):TileData
	{
		var color:Int = map.getPixel(tileX, tileY);
		
		return tilesData[color];
	}
	
	public function isSolid(tileX:Int, tileY:Int):Bool
	{
		return getTile(tileX, tileY).solid;
	}
	
	// PATH FINDING ///////////////////////////////////////////////////////////////////////////////
	
	// http://qiao.github.io/PathFinding.js/visual/
	// https://github.com/qiao/PathFinding.js/tree/master/src
	// http://forums.mediabox.fr/wiki/tutoriaux/flashplatform/jeux/pathfinder_algorithme_astar
	// http://www.gamasutra.com/view/feature/131505/toward_more_realistic_pathfinding.php?print=1
	
	static private inline var NODE_DISTANCE:Int = 10;
	
	private var mapNode:Array<Array<Node>>;
	private var openList:Array<Node>;
	private var closeList:Array<Node>;
	
	public function getPath(startPoint:Point, endPoint:Point, origin:Point):Array<Node>
	{
		var path:Array<Node> = [];
		
		openList = [];
		closeList = [];
		
		var originX:Int = Std.int(origin.x / context.tileW);
		var originY:Int = Std.int(origin.y / context.tileW);
		var startTileX:Int = Std.int(startPoint.x / context.tileW);
		var startTileY:Int = Std.int(startPoint.y / context.tileH);
		var endTileX:Int = Std.int(endPoint.x / context.tileW);
		var endTileY:Int = Std.int(endPoint.y / context.tileH);
		
		setMapNode(originX, originY);
		
		var startNode:Node = getNode(startTileX, startTileY);
		var endNode:Node = getNode(endTileX, endTileY);
		
		if (endNode.isWalkable)
		{
			var currentNode:Node;
			var neighbours:Array<Node>;
			var maxNeighbours:Int;
			var node:Node;
			var newG:Int;
			var newH:Int;
			var newF:Int;
			var lastNode:Node;
			
			addToOpenList(startNode);
			
			while (openList.length > 0)
			{
				currentNode = getCurrentNode();
				
				if (currentNode == endNode)
					break;
				
				addToCloseList(currentNode);
				
				neighbours = getNeighbours(currentNode);
				
				maxNeighbours = neighbours.length;
				
				for (i in 0...maxNeighbours)
				{
					node = neighbours[i];
					
					if (isOnCloseList(node) || !node.isWalkable)
						continue;
					
					newG = node.parent.g + NODE_DISTANCE;
					newH = Std.int(Math.abs(endNode.row - node.row) + Math.abs(endNode.col - node.col)) * NODE_DISTANCE;
					newF = newG + newH;
					
					if (isOnOpenList(node))
					{
						if (newG < node.g)
						{
							node.parent = currentNode;
							node.g = newG;
							node.h = newH;
							node.f = newF;
						}
					}
					else
					{
						addToOpenList(node);
						
						node.parent = currentNode;
						node.g = newG;
						node.h = newH;
						node.f = newF;
					}
					
					trace(node.col, node.row, node.parent.col, node.parent.row);
				}
				
				if (openList.length != 0)
				{
					lastNode = endNode;
					
					while (lastNode != startNode)
					{
						path.push(lastNode);
						
						lastNode = lastNode.parent;
					}
					
					path.reverse();
				}
			}
		}
		
		return path;
	}
	
	private function setMapNode(originTileX:Int, originTileY:Int):Void
	{
		mapNode = [];
		
		var node:Node;
		var tile:TileData;
		
		for (col in originTileX...originTileX + context.nbCol)
		{
			for (row in originTileY...originTileY + context.nbRow)
			{
				tile = getTile(col, row);
				
				node = new Node();
				
				node.col = col;
				node.row = row;
				node.isWalkable = !tile.solid;
				
				if (mapNode[col] == null)
					mapNode[col] = [];
				
				mapNode[col][row] = node;
			}
		}
	}
	
	private function getNode(col:Int, row:Int):Node
	{
		return mapNode[col][row];
	}
	
	private function addToOpenList(node:Node):Void
	{
		removeFromCloseList(node);
		openList.push(node);
	}
	
	private function addToCloseList(node:Node):Void
	{
		removeFromOpenList(node);
		closeList.push(node);
	}
	
	private function removeFromOpenList(node:Node):Void
	{
		var tmpList:Array<Node> = [];
		var max:Int = openList.length;
		var n:Node;
		
		for (i in 0...max)
		{
			n = openList[i];
			
			if (n != node)
				tmpList.push(n);
		}
		
		openList = tmpList;
	}
	
	private function removeFromCloseList(node:Node):Void
	{
		var tmpList:Array<Node> = [];
		var max:Int = closeList.length;
		var n:Node;
		
		for (i in 0...max)
		{
			n = closeList[i];
			
			if (n != node)
				tmpList.push(n);
		}
		
		closeList = tmpList;
	}
	
	private function getCurrentNode():Node
	{
		var tmpList:Array<Node> = [];
		var max:Int = openList.length;
		var minF:Int = 1000000;
		var current:Node = null;
		var n:Node;
		
		for (i in 0...max)
		{
			n = openList[i];
			
			if (n.f < minF)
			{
				minF = n.f;
				current = n;
			}
		}
		
		return current;
	}
	
	private function getNeighbours(node:Node):Array<Node>
	{
		var neighbours:Array<Node> = [];
		var maxCol:Int = context.nbCol;
		var maxRow:Int = context.nbRow;
		
		if (node.col - 1 >= 0)
			neighbours.push(getNode(node.col - 1, node.row));
		if (node.col + 1 < map.width)
			neighbours.push(getNode(node.col + 1, node.row));
		if (node.row - 1 >= 0)
			neighbours.push(getNode(node.col, node.row - 1));
		if (node.row + 1 < map.height)
			neighbours.push(getNode(node.col, node.row + 1));
		
		return neighbours;
	}
	
	private function isOnOpenList(node:Node):Bool
	{
		var max:Int = openList.length;
		
		for (i in 0...max)
		{
			if (openList[i] == node)
				return true;
		}
		
		return false;
	}
	
	private function isOnCloseList(node:Node):Bool
	{
		var max:Int = closeList.length;
		
		for (i in 0...max)
		{
			if (closeList[i] == node)
				return true;
		}
		
		return false;
	}
}

class Node
{
	public var parent:Node = this;
	public var col:Int;
	public var row:Int;
	public var isWalkable:Bool = true;
	public var g:Int = 0;
	public var f:Int = 0;
	public var h:Int = 0;
}